#include <algorithm/analytical_model/algorithm/pipeline/AnalyticalPipelineParam.h>
#include <algorithm/analytical_model/io/json/converter/AllConverters.h>
#include <algorithm/analytical_model/io/json/transform/TransformJSON.h>
#include <algorithm/shared/tool/stringHash/StringHash.hpp>
#include <utils/Format.h>
#include <utils/Print.h>
#include <utils/config.h>

#include <boost/iostreams/device/mapped_file.hpp>
#include <boost/iostreams/stream.hpp>

#include <stdexcept>

using namespace solar;

auto solar::loadAnalyticalPipelineParam(
    const io::JSONType& /*unused*/, const boost::json::value& json_value) -> AnalyticalPipelineParam
{
    boost::json::value common_para_value;
    boost::json::value rectangle_grid_value;
    boost::json::value scene_para_value;
    boost::json::value simulation_para_value;
    boost::json::value sun_value;
    std::vector<boost::json::value> heliostat_values;
    std::vector<boost::json::value> receiver_values;
    std::vector<boost::json::value> heliostat_para_values;
    solar::io::json::transformJSON(solar::io::json::DeCompressTag(), json_value, common_para_value,
                                   rectangle_grid_value, scene_para_value, simulation_para_value,
                                   sun_value, heliostat_values, receiver_values,
                                   heliostat_para_values);
    AnalyticalPipelineParam result;
    result.common_para_ = boost::json::value_to<CommonPara>(common_para_value);
    result.scene_para_ = boost::json::value_to<ScenePara>(scene_para_value);
    result.simulation_para_ = boost::json::value_to<SimulationPara>(simulation_para_value);
    result.rectangle_grid_ = boost::json::value_to<analytical::RectangleGrid>(rectangle_grid_value);
    result.heliostats_.resize(heliostat_values.size());
    result.heliostat_paras_.resize(heliostat_values.size());
    result.receivers_.resize(receiver_values.size());
#pragma omp parallel
    {
#pragma omp for nowait
        for (int i = 0; i < heliostat_values.size(); i++)
        {
            result.heliostats_[i] = boost::json::value_to<Heliostat>(heliostat_values[i]);
        }
#pragma omp for nowait
        for (int i = 0; i < receiver_values.size(); i++)
        {
            result.receivers_[i] = boost::json::value_to<Receiver>(receiver_values[i]);
        }
#pragma omp for nowait
        for (int i = 0; i < heliostat_values.size(); i++)
        {
            result.heliostat_paras_[i] =
                boost::json::value_to<HeliostatPara>(heliostat_para_values[i]);
        }
    }

    result.sun_ = boost::json::value_to<Sun>(sun_value);
    const auto* value_ptr = json_value.if_object();
    if (const auto* model_type_value = value_ptr->if_contains("model_type");
        model_type_value != nullptr)
    {
        switch (hash(model_type_value->get_string().c_str()))
        {
        case "convolution"_hash:
            result.model_type_ = ModelType::kConvolution;
            break;
        case "simplified"_hash:
            result.model_type_ = ModelType::kSimplified;
            break;
        default:
        {
            const auto error_string =
                solar::format("ERROR: In function {}:\n Unknown model type: {}",
                              PRETTY_FUNCTION_NAME, model_type_value->get_string());
            solar::println(error_string);
            throw std::invalid_argument("Unknown model type");
        }
        }
    }
    if (const auto* model_name_value = value_ptr->if_contains("model_name");
        model_name_value != nullptr)
    {
        switch (hash(model_name_value->get_string().c_str()))
        {
        case "HFLCAL"_hash:
            result.detailed_model_type_ = SimplifiedModelType::kHFLCAL;
            break;
        case "iHFLCAL"_hash:
            result.detailed_model_type_ = SimplifiedModelType::kiHFLCAL;
            break;
        case "bHFLCAL"_hash:
            result.detailed_model_type_ = SimplifiedModelType::kbHFLCAL;
            break;
        case "NEG"_hash:
            result.detailed_model_type_ = SimplifiedModelType::kNEG;
            break;

        case "UNIZAR"_hash:
            result.detailed_model_type_ = ConvolutionModelType::kUNIZAR;
            break;
        case "iCauchy"_hash:
            result.detailed_model_type_ = ConvolutionModelType::kiCauchy;
            break;
        case "Huang"_hash:
            result.detailed_model_type_ = ConvolutionModelType::kHuang;
            break;
        case "UNIZAR2"_hash:
            result.detailed_model_type_ = ConvolutionModelType::kUNIZAR2;
            break;
        default:
        {
            const auto error_string =
                solar::format("ERROR: In function {}:\n Unknown model: {}", PRETTY_FUNCTION_NAME,
                              model_name_value->get_string());
            solar::println(error_string);
            throw std::invalid_argument("Unknown model");
        }
        }
    }
    return result;
}

auto solar::loadAnalyticalPipelineParam(const io::JSONType& tag, const std::string_view& file_path)
    -> AnalyticalPipelineParam
{
    boost::iostreams::mapped_file_source input_buffer(file_path.data());
    input_buffer.close();
    input_buffer.open(file_path.data());
    boost::json::stream_parser parser;
    parser.reset();
    parser.write(input_buffer.data(), input_buffer.size());
    parser.finish();
    auto value = parser.release();
    const auto* value_ptr = value.if_object();
    if (value_ptr == nullptr)
    {
        return {};
    }
    return loadAnalyticalPipelineParam(tag, value);
}
